#pragma once

#include "sfc/io.h"

namespace sfc::test {

using panicking::Location;

struct AssertFailed {};

template <class T>
struct Param {
  Str name;
  const T& value;

  void fmt(auto& f) const {
    f.write_fmt("{} = {}", name, value);
  }
};

template <class T>
Param(Str, const T&) -> Param<T>;

void assert_failed(Location loc, const auto& msg, const auto&... pars) {
  io::println("assert failed `{}`:", msg);
  io::println("> at {}[{}]", loc.file, loc.line);
  (void)(io::println("  {}", pars), ...);
  throw AssertFailed{};
}

void assert_true(const auto& val, Location loc = {}) {
  if (bool(val)) {
    return;
  }
  assert_failed(loc, "val==true", Param{"val", val});
}

void assert_false(const auto& val, Location loc = {}) {
  if (!bool(val)) {
    return;
  }
  assert_failed(loc, "val==false", Param{"val", val});
}

void assert_eq(const auto& a, const auto& b, Location loc = {}) {
  if (a == b) {
    return;
  }
  assert_failed(loc, "(lhs == rhs)", Param{"lhs", a}, Param{"rhs", b});
}

void assert_ne(const auto& a, const auto& b, Location loc = {}) {
  if (a != b) {
    return;
  }
  assert_failed(loc, "(lhs != rhs)", Param{"lhs", a}, Param{"rhs", b});
}

void assert_flt_eq(const auto& a, const auto& b, Location loc = {}) {
  if (num::flt_eq(a, b)) {
    return;
  }
  assert_failed(loc, "(lhs == rhs)", Param{"lhs", a}, Param{"rhs", b});
}

void assert_flt_ne(const auto& a, const auto& b, Location loc = {}) {
  if (!num::flt_eq(a, b)) {
    return;
  }
  assert_failed(loc, "(lhs != rhs)", Param{"lhs", a}, Param{"rhs", b});
}

}  // namespace sfc::test

#define ASSERT_ANY_THROW(...)
